package com.atguigu.gmall.item.controller;

import com.atguigu.gmall.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "/hello")
@Slf4j
public class HelloController {

    RSemaphore semaphore = null ;
    // @PostConstruct
    public void init() {
        semaphore = redissonClient.getSemaphore("test-semaphore");
        semaphore.addPermits(2);		// 分配两个许可证
    }

    @GetMapping(value = "/semaphore")   // 限流
    public Result semaphore() throws InterruptedException {
        semaphore.acquire();  // 申请许可证
        log.info(Thread.currentThread().getName() + "----> 申请到了一个许可证...");
        Thread.sleep(500);
        semaphore.release();
        log.info(Thread.currentThread().getName() + "----> 归还了一个许可证....");
        return Result.ok() ;
    }

    @Autowired
    private RedissonClient redissonClient ;     // 实现的分布式锁用的是Redis中的hash结构

    private String value = null ;

    @GetMapping(value = "/read")
    public Result read() throws InterruptedException {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("read-write-lock");
        RLock readLock = readWriteLock.readLock();
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读锁加锁成功，执行业务操作...");
            TimeUnit.SECONDS.sleep(10);
            return Result.ok(value) ;
        }finally {
            readLock.unlock();
            System.out.println(Thread.currentThread().getName() + "读锁已经释放了...");
        }
    }

    @GetMapping(value = "/write")
    public Result write() throws InterruptedException {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("read-write-lock");
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写锁锁加锁成功，执行业务操作...");
            TimeUnit.SECONDS.sleep(10);
            value = UUID.randomUUID().toString().replace("-" ,"") ;
            return Result.ok(value) ;
        }finally {
            writeLock.unlock();
            System.out.println(Thread.currentThread().getName() + "写锁已经释放了...");
        }
    }

    @GetMapping(value = "/testLock02")
    public void testLock02() {

        // 加锁
        RLock lock = redissonClient.getLock("redisson-lock");       // 获取到锁就是一个可重入锁，可重入性实现就是对锁的value进行+1或者-1的操作
        lock.lock();

        // 获取到了锁
        System.out.println("获取到了锁" + Thread.currentThread().getName());
        testLock02();

        lock.unlock();

    }

    @GetMapping(value = "/testLock01")
    public Result testLock01() throws InterruptedException {

        // 加锁
        RLock lock = redissonClient.getLock("redisson-lock");
        // lock.lock();            // 默认的超时时间为30s
        // lock.lock(60 , TimeUnit.SECONDS);
        boolean tryLock = lock.tryLock();       // 尝试加锁，如果加锁成功了返回true，加锁失败了返回false。不会阻塞未获取到锁的线程
        // boolean tryLock = lock.tryLock(10, TimeUnit.SECONDS);       // 第一个参数表示获取锁的最大等待时间

        /**
         * 如果没有给锁显式的指定过期时间，那么Redisson工具内部会存在续期机制
         * 如果给锁显式的指定了过期时间，那么Redisson工具就不会给锁进行续期
         * 每隔锁的过期时间的1/3的时候进行锁续期，续期的时间是30s
         * 底层的原理：续期是通过一个独立的线程完成的
         */
//        boolean tryLock = lock.tryLock(0, 60, TimeUnit.SECONDS);
        if(tryLock) {

            // 面试题: 在你的项目开发中有没有遇到过一些问题，你是怎么解决的?

            try {

                // 获取到了锁
                System.out.println("获取到了锁" + Thread.currentThread().getName());

                // 执行业务操作
                System.out.println("执行业务操作");
                // TimeUnit.SECONDS.sleep(60);
                int a = 1 / 0 ;

            }catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 释放锁
                lock.unlock();
                System.out.println("释放了锁" + Thread.currentThread().getName());
            }

        }else {
            System.out.println("没有获取到锁" + Thread.currentThread().getName());
        }

        // 返回结果
        return Result.ok() ;

    }

    @Autowired
    private RedisTemplate<String , String> redisTemplate ;

    // private static final Lock lock = new ReentrantLock() ;

    // 如果业务的执行时长超过了锁的过期时间，那么会不会存在问题? 在某一个时刻多个线程访问了同样业务代码，锁失效了没有意义。
    // 怎么解决?
    // 解决方案一：给锁设置一个较长的过期时间(时间的确定：对业务进行大量的测试，得出最大的访问时间)
    // 解决方案二: 给锁进行续期(看门狗机制)
    //            锁续期的方案：1、开启一个守护线程  2、周期性执行的任务  ----> 每隔10s执行一次任务，任务的作用就是给锁进行续期，把锁的时间设置为最初的时间

    @GetMapping(value = "/count")
    public String count() {

        // 加锁
        String uuid = UUID.randomUUID().toString().replace("-", "");
        lock(uuid) ;

        try {
            // 调用业务方法
            incr();
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            unlock(uuid) ;
        }

        return "ok" ;
    }

    public void lock(String uuid) {

        while(!redisTemplate.opsForValue().setIfAbsent("lock" , uuid , 30 , TimeUnit.SECONDS)) {}  // 返回的如果是true说明加锁成功，返回的是false就说明加锁失败

        // 给锁设置过期时间：为了解决执行业务代码的时候服务器宕机所产生的死锁问题
        // redisTemplate.expire("lock" , 30 , TimeUnit.SECONDS) ;

        // 开启一个周期性执行任务，对所进行续期
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            redisTemplate.expire("lock" ,30 , TimeUnit.SECONDS) ;
        } , 5 , 10 , TimeUnit.SECONDS) ;        // 5表示第一次执行任务是的延迟时间、10表示的后续执行任务时所对应的时间间隔

    }

    public void unlock(String uuid) {

        /**
         * lua：就是一门脚本语言(不需要进行编译，在执行的时候是通过解释器进行解释执行)
         * 官网：http://www.lua.org/manual/5.4/
         * 在Redis中如何去使用lua脚本：
         */
        String script = "if redis.call(\"get\" , KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end" ;

        // 执行lua脚本
        // RedisScript: 封装script数据
        // List<K> keys: key的list集合
        // Object... args: 参数数组
        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
        if(result == 1) {       // 释放锁成功了
            System.out.println("释放锁成功了...");
        }else {
            System.out.println("锁是别人的...");
        }

    }

    // 业务方法
    public void incr() {

        try {
            TimeUnit.SECONDS.sleep(60) ;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String count = redisTemplate.opsForValue().get("count");
        if(StringUtils.isEmpty(count)) {
            redisTemplate.opsForValue().set("count" , "1");
        }else {
            int parseInt = Integer.parseInt(count);
            parseInt++ ;
            redisTemplate.opsForValue().set("count" , String.valueOf(parseInt));
        }

    }

}